Passed
Push — master ( d9e5dd...36764d )
by Spuds
01:07 queued 26s
created

elk_jquery_embed.js ➔ getVimeoIMG   A

Complexity

Conditions 3

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 7
dl 0
loc 12
rs 10
c 0
b 0
f 0
1
/*!
2
 * @package   ElkArte Forum
3
 * @copyright ElkArte Forum contributors
4
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file)
5
 *
6
 * @version 1.1.9
7
 *
8
 * Original code from Aziz, redone and refactored for ElkArte
9
 */
10
11
/** global: elk_session_id, elk_session_var, elk_scripturl */
12
13
/**
14
 * This javascript searches the message for video links and replaces them
15
 * with a clickable preview thumbnail of the video.  Once the image is clicked
16
 * the video is embedded in to the page to play.
17
 *
18
 * Currently, works with YouTube, Vimeo, TikTok and DailyMotion
19
 *
20
 */
21
(function ($)
22
{
23
	'use strict';
24
25
	/**
26
	 * @param {object} oInstanceSettings holds the text strings to use in the html created
27
	 * @param {int} msgid optional to only search for links in a specific id
28
	 */
29
	$.fn.linkifyvideo = function (oInstanceSettings, msgid)
30
	{
31
		let oDefaultsSettings = {
32
			embed_limit: 25,
33
			preview_image: '',
34
			ctp_video: '',
35
			hide_video: '',
36
			youtube: '',
37
			vimeo: '',
38
			dailymotion: '',
39
			tiktok: ''
40
		};
41
42
		// Account for user options
43
		let oSettings = $.extend({}, oDefaultsSettings, oInstanceSettings || {});
44
45
		/**
46
		 * Replaces the image with the created embed code to show the video
47
		 * Called from click event attached to the image
48
		 *
49
		 * @param {string} tag anchor tag we are replacing with the embed tag
50
		 * @param {string} eURL the load or place source link
51
		 */
52
		function showEmbed(tag, eURL)
53
		{
54
			$(tag).html(embed_html.replace('{src}', eURL));
0 ignored issues
show
Bug introduced by
The local (let) variable embed_html is used before it is defined. This will cause a reference error.
Loading history...
55
		}
56
57
		/**
58
		 * Shows the video image and sets up the link
59
		 * Sets click event to load video sites embed code
60
		 *
61
		 * @param {object} a videoID link
62
		 * @param {string} src source of image
63
		 * @param {string} eURLa play link
64
		 */
65
		function getIMG(a, src, eURLa)
66
		{
67
			return $('' +
68
				'<div class="elk_video">' +
69
				'   <a href="' + a.href + '">' +
70
				'       <img class="elk_video_preview" alt="' + oSettings.preview_image + '" ' + 'title="' + oSettings.ctp_video + '" src="' + src + '"/>' +
71
				'   </a>' +
72
				'</div>')
73
				.on('click', function (e)
74
					{
75
						e.preventDefault();
76
						let tag = this;
77
						showEmbed(tag, eURLa);
78
					}
79
				);
80
		}
81
82
		/**
83
		 * Returns a linked preview image.  Click on the image to load the player.
84
		 *
85
		 * @param {string} a link tag of the video
86
		 * @param {string} src link of the preview image
87
		 * @param {string} eURLa single click event play video
88
		 */
89
		function embedIMG(a, src, eURLa)
90
		{
91
			return getIMG(a, src, eURLa);
92
		}
93
94
		/**
95
		 * Creates and inserts a document fragment.  Doing this vs inner/outer HTML ensures that any script
96
		 * tags in the embed code will execute.
97
		 *
98
		 * @param {Element} a the link we are working with
99
		 * @param {object} data the data from the ajax call
100
		 */
101
		function createFragment(a, data)
0 ignored issues
show
introduced by
The function createFragment does not seem to be used and can be removed.
Loading history...
102
		{
103
			// Since data.html may contain a script tag that needs to run, we have to add it like this
104
			let parent = a.parentNode,
105
				frag = document.createRange().createContextualFragment('<div class="elk_video">' + data.html + '</div>');
106
107
			parent.parentNode.appendChild(frag);
108
			parent.nextSibling.outerHTML = '';
109
		}
110
111
		// The embed code
112
		let domain_regex = /^[^:]*:\/\/(?:www\.)?([^\/]+)(\/.*)$/,
113
			embedded_count = 0,
114
			embed_html = '<iframe width="640" height="360" style="border: none;margin: 0 auto;aspect-ratio 16 / 9;max-width: 100%; max-height: 360px;" src="{src}" frameborder="0" data-autoplay="true" allow="fullscreen" loading="lazy" type="text/html"></iframe>',
0 ignored issues
show
Unused Code introduced by
The variable embed_html seems to be never used. Consider removing it.
Loading history...
115
			handlers = {},
116
			imgHandlers = {},
117
			logos = {
118
				tiktok: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48'%3E%3Cpath fill='%23fff' fill-opacity='.01' d='M0 0h48v48H0z'/%3E%3Cpath fill='%232F88FF' stroke='%23000' stroke-linejoin='round' stroke-width='3.833' d='M21.358 19.14c-5.888-.284-9.982 1.815-12.28 6.299-3.446 6.724-.597 17.728 10.901 17.728 11.499 0 11.831-11.111 11.831-12.276V17.876c2.46 1.557 4.533 2.495 6.221 2.813 1.688.317 2.76.458 3.219.422v-6.476c-1.561-.188-2.911-.547-4.05-1.076-1.709-.794-5.096-2.997-5.096-6.226.002.016.002-.817 0-2.499h-7.118c-.021 15.816-.021 24.502 0 26.058.032 2.334-1.779 5.6-5.45 5.6-3.672 0-5.482-3.263-5.482-5.367 0-1.288.442-3.155 2.271-4.538 1.085-.82 2.59-1.147 5.033-1.147V19.14Z'/%3E%3C/svg%3E",
119
				vimeo: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 455.731 455.731'%3E%3Cpath fill='%231ab7ea' d='M0 0h455.731v455.731H0z'/%3E%3Cpath fill='%23fff' d='m49.642 157.084 17.626 22.474s22.033-17.186 29.965-17.186c4.927 0 15.423 5.729 22.033 25.558 6.61 19.83 34.441 122.62 36.134 127.351 7.607 21.26 17.626 60.811 48.473 66.54s70.065-25.558 91.657-48.473c21.592-22.914 106.64-120.741 110.165-179.349 3.26-54.191-14.517-66.765-22.474-71.828-14.542-9.254-38.778-12.338-61.692-4.407s-57.726 33.931-66.98 80.2c0 0 31.287-11.457 42.744-.441s8.373 35.253-1.322 53.32-37.015 59.93-47.151 61.252c-10.135 1.322-18.067-18.508-19.389-23.796-1.322-5.288-18.067-77.997-24.236-120.3s-33.049-49.354-45.829-49.354c-12.779.001-34.812 9.696-109.724 78.439z'/%3E%3C/svg%3E",
120
				dailymotion: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath fill='%230066DC' fill-rule='evenodd' d='M0 512h512V0H0v512Zm441.5-68.635h-76.314v-29.928c-23.443 22.945-47.385 31.424-79.308 31.424-32.421 0-60.354-10.474-83.797-31.424-30.926-27.433-46.887-63.346-46.887-105.245 0-38.407 14.965-72.823 42.896-99.758 24.94-24.44 55.367-36.91 89.284-36.91 32.422 0 57.361 10.973 75.318 33.917V88.724L441.5 72.395v370.97Zm-141.157-202.01c-37.41 0-66.339 30.426-66.339 66.338 0 37.41 28.93 65.841 69.332 65.841 33.918 0 62.349-27.932 62.349-64.843 0-38.406-28.431-67.336-65.342-67.336Z'/%3E%3C/svg%3E",
121
			};
122
123
		// Get a TikTok video thumbnail and embed data
124
		imgHandlers.getTikTokEmbed = function(eURL, callback)
125
		{
126
			$.getJSON(eURL, {format: 'json'},
127
			function(data)
128
			{
129
				if (typeof data.html === 'undefined')
130
				{
131
					data.thumbnail_url = logos.tiktok;
132
					data.html = '';
133
				}
134
135
				callback(data);
136
			});
137
		};
138
139
		// Get a dailymotion video thumbnail
140
		imgHandlers.getDailymotionIMG = function(eURL, callback)
141
		{
142
			$.getJSON(eURL, {},
143
			function(data)
144
			{
145
				if (typeof data.thumbnail_480_url !== 'undefined')
146
				{
147
					callback(data.thumbnail_480_url);
148
				}
149
				else
150
				{
151
					callback(logos.dailymotion);
152
				}
153
			});
154
		};
155
156
		// Get a Vimeo video thumbnail
157
		imgHandlers.getVimeoIMG = function(videoID, callback)
158
		{
159
			$.getJSON(videoID, {format: 'json'},
160
			function(data)
161
			{
162
				if (typeof data[0].thumbnail_large !== 'undefined')
163
				{
164
					callback(data[0].thumbnail_large);
165
				}
166
				else
167
				{
168
					callback(logos.vimeo);
169
				}
170
			});
171
		};
172
173
		// Youtube and variants
174
		handlers['youtube.com'] = function (path, a)
175
		{
176
			let videoID = path.match(/\bv[=/]([^&#?$]+)/i) || path.match(/#p\/(?:a\/)?[uf]\/\d+\/([^?$]+)/i) || path.match(/(?:\/)([\w-]{11})/i);
177
178
			if (!videoID || !(videoID = videoID[1]))
179
			{
180
				return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
181
			}
182
183
			// There are two types of YouTube timestamped links
184
			// http://youtu.be/lLOE3fBZcUU?t=1m37s when you click share underneath the video
185
			// http://youtu.be/lLOE3fBZcUU?t=97 when you right click on a video and choose "Copy video URL at current time"
186
			// For embedding, you need to use "?start=97" instead, so we have to convert t=1m37s to seconds while also supporting t=97
187
			let startAt = path.match(/t=(?:([1-9]{1,2})h)?(?:([1-9]{1,2})m)?(?:([1-9]+)s?)/),
188
				startAtPar = '';
189
190
			if (startAt)
191
			{
192
				let startAtSeconds = 0;
193
194
				// Hours
195
				if (typeof startAt[1] !== 'undefined')
196
				{
197
					startAtSeconds += parseInt(startAt[1]) * 3600;
198
				}
199
200
				// Minutes
201
				if (typeof startAt[2] !== 'undefined')
202
				{
203
					startAtSeconds += parseInt(startAt[2]) * 60;
204
				}
205
206
				// Seconds
207
				if (typeof startAt[3] !== 'undefined')
208
				{
209
					startAtSeconds += parseInt(startAt[3]);
210
				}
211
212
				startAtPar = '&start=' + startAtSeconds.toString();
213
			}
214
215
			let embedURL = '//www.youtube-nocookie.com/embed/' + videoID + '?rel=0' + startAtPar,
216
				tag = embedIMG(a, '//i.ytimg.com/vi/' + videoID + '/sddefault.jpg', embedURL + '&autoplay=1');
217
218
			return [oSettings.youtube, tag];
219
		};
220
		handlers['m.youtube.com'] = handlers['youtube.com'];
221
		handlers['youtu.be'] = handlers['youtube.com'];
222
223
		// Vimeo
224
		handlers['vimeo.com'] = function (path, a)
225
		{
226
			let videoID = path.match(/^\/(\d+)/i);
227
228
			if (!videoID || !(videoID = videoID[1]))
229
			{
230
				return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
231
			}
232
233
			let embedURL = '//player.vimeo.com/video/' + videoID,
234
				imgURL = '//vimeo.com/api/v2/video/' + videoID + '.json',
235
				tag;
236
237
			tag = embedIMG(a, logos.vimeo, embedURL + '?autoplay=1');
238
239
			// Get the preview image / embed tag
240
			imgHandlers.getVimeoIMG(imgURL, function (img)
241
			{
242
				$(a).parent().next().find("img").attr("src", img);
243
			});
244
245
			return [oSettings.vimeo, tag];
246
		};
247
248
		// Dailymotion
249
		handlers['dailymotion.com'] = function (path, a)
250
		{
251
			let videoID = path.match(/^\/video\/([a-z0-9]{1,18})/i);
252
253
			if (!videoID || videoID[1] === '')
254
			{
255
				return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
256
			}
257
258
			let embedURL = '//dailymotion.com/embed/video/' + videoID[1],
259
				imgURL = '//api.dailymotion.com/video/' + videoID[1] + '?fields=thumbnail_480_url',
260
				tag;
261
262
			tag = embedIMG(a, logos.dailymotion, embedURL + '?related=0&autoplay=1');
263
264
			// Get the preview image or embed tag
265
			imgHandlers.getDailymotionIMG(imgURL, function (img)
266
			{
267
				$(a).parent().next().find('img').attr('src', img);
268
			});
269
270
			return [oSettings.dailymotion, tag];
271
		};
272
273
		// TikTok
274
		handlers['tiktok.com'] = function (path, a)
275
		{
276
			let videoID = path.match(/^\/@([0-9A-Za-z_\-.]*)\/video\/([0-9]*)/i);
277
278
			if (!videoID)
279
			{
280
				return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
281
			}
282
283
			let embedURL = '//www.tiktok.com/oembed?url=https://www.tiktok.com/@' + videoID[1] + '/video/' + videoID[2],
284
				tag;
285
286
			imgHandlers.getTikTokEmbed(embedURL, function (data)
287
			{
288
				$(a).parent().next().find('img').attr('src', data.thumbnail_url);
289
				a.embedURL = data.html;
290
			});
291
292
			tag = embedIMG(a, logos.tiktok, embedURL);
293
294
			// Change the default click event to one that replaces the markup, also prepare for a 9/16 video
295
			tag.off('click', '**' , false);
296
			tag.on('click', function (e)
297
			{
298
				e.preventDefault();
299
				let load = $(a).parent();
300
				load.addClass('portrait');
301
302
				load.next().replaceWith('<div class="elk_video portrait">' + a.embedURL + '</div>');
303
			});
304
305
			return [oSettings.tiktok, tag];
306
		};
307
308
		// ---------------------------------------------------------------------------
309
		// Get the bbc_link links in the id="msg_1234 divs.
310
		let links;
311
312
		if (typeof msgid !== 'undefined')
313
		{
314
			links = document.querySelectorAll('#' + msgid + ' a.bbc_link');
315
		}
316
		else
317
		{
318
			links = document.querySelectorAll('[id^=msg_] a.bbc_link');
319
		}
320
321
		// Create the show/hide button
322
		let showhideBtn = $('' +
323
			'<a class="floatright" title="' + oSettings.hide_video + '">' +
324
			'   <i class="icon icon-small i-chevron-up" alt=">"></i>' +
325
			'</a>')
326
			.on('click', function()
327
			{
328
				let $img = $(this).find("i"), // The open / close icon
329
					$vid = $(this).parent().next(); // The immediate elk_video div
330
331
				// Toggle slide the video and change the icon
332
				$img.attr("class", "icon icon-small " + ($vid.is(":hidden") !== true ? "i-chevron-down" : "i-chevron-up"));
333
				$vid.slideToggle();
334
			});
335
336
		// Loop though each link
337
		links.forEach((link) =>
338
		{
339
			let tag = link,
340
				text = tag.innerText || tag.textContent || '';
341
342
			// Ignore in sentences
343
			if (tag.previousSibling && tag.previousSibling.nodeName === '#text' && tag.previousSibling.nodeValue !== ' ')
344
			{
345
				return;
346
			}
347
348
			// Ignore in quotes and signatures
349
			if ("bbc_quote;signature".indexOf(tag.parentNode.className) !== -1)
350
			{
351
				return;
352
			}
353
354
			// No href or inner text not equal to href attr then we move along
355
			if (tag.href === "" || tag.href.indexOf(text) !== 0)
356
			{
357
				return;
358
			}
359
360
			// Get domain and validate we know how to handle it
361
			let m = tag.href.match(domain_regex),
362
				handler = null,
0 ignored issues
show
Unused Code introduced by
The assignment to handler seems to be never used. If you intend to free memory here, this is not necessary since the variable leaves the scope anyway.
Loading history...
363
				args = null;
0 ignored issues
show
Unused Code introduced by
The assignment to args seems to be never used. If you intend to free memory here, this is not necessary since the variable leaves the scope anyway.
Loading history...
364
365
			// One of our video provider domains?
366
			if (embedded_count < oSettings.embed_limit && m !== null && typeof handlers[m[1]] !== 'undefined' && handlers[m[1]] !== null)
367
			{
368
				// Call the handler and get the tag to insert
369
				handler = handlers[m[1]];
370
371
				args = handler(m[2], tag);
372
				if (args)
373
				{
374
					embedded_count++;
375
					$(tag).wrap('<div class="elk_video_container">');
376
					$(tag).wrap('<div class="elk_videoheader">').text(args[0]).after(showhideBtn.clone(true));
377
					$(tag).parent().after(args[1]);
378
				}
379
			}
380
		});
381
	};
382
})(jQuery);
383